Skip to content

wp_kses: add support for picture element and srcset attribute on img tags#6184

Open
adamsilverstein wants to merge 30 commits intoWordPress:trunkfrom
adamsilverstein:ticket/29807
Open

wp_kses: add support for picture element and srcset attribute on img tags#6184
adamsilverstein wants to merge 30 commits intoWordPress:trunkfrom
adamsilverstein:ticket/29807

Conversation

@adamsilverstein
Copy link
Copy Markdown
Member

Trac ticket: https://core.trac.wordpress.org/ticket/29807


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions
Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread tests/phpunit/tests/kses.php Outdated

This comment was marked as outdated.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for the HTML5 <picture> element and srcset attribute on <img> tags to WordPress's KSES filtering system. This enables proper handling of responsive images in content filtering.

  • Adds <picture> and <source> elements to allowed post tags
  • Adds srcset and sizes attributes to <img> and <source> elements
  • Refactors URI sanitization logic to handle multi-URI attributes like srcset

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/wp-includes/kses.php Adds picture/source elements to allowed tags, adds srcset/sizes attributes, and creates new wp_kses_sanitize_uris function to handle multi-URI attributes
tests/phpunit/tests/kses.php Adds comprehensive test coverage for img srcset attributes, srcset sanitization, and picture element filtering

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
@vishalkakadiya
Copy link
Copy Markdown

@adamsilverstein Can you resolve conflicts on this PR, as the testing team has to apply this patch to QA it? Thanks!

https://core.trac.wordpress.org/ticket/29807#comment:53

@adamsilverstein
Copy link
Copy Markdown
Member Author

@adamsilverstein Can you resolve conflicts on this PR, as the testing team has to apply this patch to QA it? Thanks!

https://core.trac.wordpress.org/ticket/29807#comment:53

yes, I will work on that.

@adamsilverstein adamsilverstein force-pushed the ticket/29807 branch 2 times, most recently from c402114 to f9e002c Compare February 10, 2026 00:48
@juanmaguitar
Copy link
Copy Markdown

@adamsilverstein thanks for working on this ticket!
Do you think you'll be able to address the requested changes in time for WP 7.0 RC1 (happening in ~1 week)?

# Conflicts:
#	src/wp-includes/kses.php
@adamsilverstein
Copy link
Copy Markdown
Member Author

@adamsilverstein thanks for working on this ticket! Do you think you'll be able to address the requested changes in time for WP 7.0 RC1 (happening in ~1 week)?

I have resolved the merge conflicts and am looking at adding additional tests. I'll also review feedback on the Trac ticket. Also fine punting this to 7.1 early.

@adamsilverstein adamsilverstein marked this pull request as ready for review March 13, 2026 17:49
@github-actions
Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props adamsilverstein, azaozz, vishalkakadiya, juanmaguitar.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Tests currently fail, documenting three bugs:
- CDN resizer URLs with commas in paths are incorrectly
  split by wp_kses_sanitize_uris() naive comma splitting
- Original spacing around commas is not preserved
- decoding and fetchpriority attrs stripped from img tags

Also adds passing coverage tests for wp_kses_uri_attributes,
wp_kses_one_attr srcset handling, source/picture element
edge cases, and the URI attributes filter hook.
These performance attributes are commonly added by WordPress
core (wp_img_tag_add_loading_optimization_attrs) and should
not be stripped when content passes through KSES filtering.
CDN image resizers (e.g. Cloudflare) use commas in URL paths
like cdn-cgi/image/format=auto,quality=80,width=412/...
which were incorrectly split by the naive comma-based
preg_split in wp_kses_sanitize_uris(). This rewrites the
splitting to use the srcset descriptor pattern (e.g. 480w,
2x) as entry boundaries, preserving commas within URLs.
Also preserves original whitespace around separators instead
of normalizing with implode.
WordPress coding standards require aligned double arrows
in multi-line arrays. Adjusts spacing so all arrows in the
data provider align with the longest key.
@adamsilverstein
Copy link
Copy Markdown
Member Author

I added some fixes and additional tests.

Expand test coverage for ticket #29807 with edge cases:
CDN URLs with gravity=0.5x0.5 parameters, decimal pixel
density descriptors, picture element type fallbacks and
art direction, protocol-relative URLs, and descriptor-less
final srcset entries. Update @SInCE to 7.1.0 per milestone.
@adamsilverstein
Copy link
Copy Markdown
Member Author

Refreshed against current trunk (clean merge, CI green). Given 7.0 RC1 has passed, now targeting 7.1.

Comment thread src/wp-includes/kses.php
'srcset' => true,
'usemap' => true,
'width' => true,
'sizes' => true,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This attribute seems it should be moved up to appear before src to maintain alphabetic ordering.

Comment thread src/wp-includes/kses.php
Comment on lines +305 to +308
'srcset' => true,
'type' => true,
'media' => true,
'sizes' => true,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort?

Comment thread src/wp-includes/kses.php
Comment on lines +1678 to +1691
* @param string $attrname The attribute name to test.
* @param string $attrvalue The attribute value to sanitize.
* @param string[] $allowed_protocols Array of allowed URL protocols.
* @param string[] $multi_uri Optional. Attributes that can contain multiple URIs. Default is array( 'srcset' ).
* @return string Sanitized attribute value.
*/
function wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols, $multi_uri = array( 'srcset' ) ) {
$uris = wp_kses_uri_attributes();

if ( ! in_array( strtolower( $attrname ), $uris, true ) ) {
return $attrvalue;
}

if ( in_array( strtolower( $attrname ), $multi_uri, true ) ) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add underscores to some vars, like $attr_name and $attr_value, and then let's add attrs to others, including $multi_uri_attrs and $uri_attrs. Otherwise, I was reading this and I was confused why attribute names would be among URIs.

Comment thread src/wp-includes/kses.php
* @param string[] $multi_uri Optional. Attributes that can contain multiple URIs. Default is array( 'srcset' ).
* @return string Sanitized attribute value.
*/
function wp_kses_sanitize_uris( $attrname, $attrvalue, $allowed_protocols, $multi_uri = array( 'srcset' ) ) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these can add PHP type hints as well for the params and the return value.

Comment thread src/wp-includes/kses.php
if ( preg_match( '/^(\s*)(.*?)(\s+\d+[wx])?\s*$/i', $entry, $m ) ) {
$leading_ws = $m[1];
$url = $m[2];
$descriptor = isset( $m[3] ) ? $m[3] : '';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$descriptor = isset( $m[3] ) ? $m[3] : '';
$descriptor = $m[3] ?? '';

Comment thread src/wp-includes/kses.php
* Instead, split on: whitespace + descriptor + comma (+ optional whitespace).
* This correctly identifies only the commas that separate srcset entries.
*/
$parts = preg_split( '/(\s+\d+[wx]\s*,\s*)/i', $attrvalue, -1, PREG_SPLIT_DELIM_CAPTURE );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$parts = preg_split( '/(\s+\d+[wx]\s*,\s*)/i', $attrvalue, -1, PREG_SPLIT_DELIM_CAPTURE );
$parts = array_filter( (array) preg_split( '/(\s+\d+[wx]\s*,\s*)/i', $attrvalue, -1, PREG_SPLIT_DELIM_CAPTURE ) );

Comment thread src/wp-includes/kses.php
Comment on lines +1707 to +1713
for ( $i = 0, $len = count( $parts ); $i < $len; $i++ ) {
if ( preg_match( '/^\s+\d+[wx]\s*,\s*$/i', $parts[ $i ] ) ) {
// This is a delimiter: space + descriptor + comma. Append it as-is.
$result .= $parts[ $i ];
} else {
// This is a URL (possibly with a trailing descriptor for the last entry).
$entry = $parts[ $i ];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using foreach can simplify this I believe.

Suggested change
for ( $i = 0, $len = count( $parts ); $i < $len; $i++ ) {
if ( preg_match( '/^\s+\d+[wx]\s*,\s*$/i', $parts[ $i ] ) ) {
// This is a delimiter: space + descriptor + comma. Append it as-is.
$result .= $parts[ $i ];
} else {
// This is a URL (possibly with a trailing descriptor for the last entry).
$entry = $parts[ $i ];
foreach( $parts as $entry ) {
if ( preg_match( '/^\s+\d+[wx]\s*,\s*$/i', $entry ) ) {
// This is a delimiter: space + descriptor + comma. Append it as-is.
$result .= $entry;
} else {
// This is a URL (possibly with a trailing descriptor for the last entry).
if ( preg_match( '/^(\s*)(.*?)(\s+\d+[wx])?\s*$/i', $entry, $m ) ) {

Comment thread src/wp-includes/kses.php
} else {
// This is a URL (possibly with a trailing descriptor for the last entry).
$entry = $parts[ $i ];
if ( preg_match( '/^(\s*)(.*?)(\s+\d+[wx])?\s*$/i', $entry, $m ) ) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use $matches instead of $m to avoid abbreviation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants